/**
 * Copyright (C) 2012 Daniel Gee.
 * See LICENSE.txt for more details.
 */
package com.rl.gui;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Rectangle;

import javax.swing.JPanel;

import com.rl.dungeon.Location;

/**
 * Displays a rendering of an {@link AppearanceMap}.
 * 
 * @author Daniel Gee
 */
public class AppearancePanel extends JPanel
{
	/**
	 * for serialization
	 */
	private static final long serialVersionUID = -4531723950083815708L;

	/**
	 * The Location that the display was scrolled to the last time it was displayed. Needed for the
	 * {@link #locationOf(int, int)} method to work.
	 */
	protected Location lastScroll;

	/**
	 * The grid we'll be displaying in the Panel space.
	 */
	private AppearanceMap appearanceMap = null;

	/**
	 * Cell height, calculated by the font used during repainting, but required for locating cells
	 * in between repaints.
	 */
	private int cellHeight;

	/**
	 * Cell width, calculated by the font used during repainting, but required for locating cells in
	 * between repaints.
	 */
	private int cellWidth;

	/**
	 * How many pixels are added to the placement of each line as compared to what the font itself
	 * specifies. Various fonts and sizes will need this to be adjusted to make the screen print
	 * compactly but without overlap.
	 */
	private int lineOffset = -3;

	/**
	 * pixels of offset in the x (horizontal) direction
	 */
	private int xOffset = 2;

	/**
	 * pixels of offset in the y (vertical) direction.
	 */
	private int yOffset = -2;

	/**
	 * Makes a new instance with 12pt "Monospaced" as the font.
	 */
	public AppearancePanel()
	{
		this.setFocusable(true);
		this.setFont(new Font("Monospaced", Font.PLAIN, 12));
		this.lastScroll = null;
	}

	/**
	 * @return the appearanceMap
	 */
	public AppearanceMap getAppearanceMap()
	{
		return this.appearanceMap;
	}

	/**
	 * @return the lineOffset
	 */
	public int getLineOffset()
	{
		return this.lineOffset;
	}

	/**
	 * @return the xOffset
	 */
	public int getxOffset()
	{
		return this.xOffset;
	}

	/**
	 * @return the yOffset
	 */
	public int getyOffset()
	{
		return this.yOffset;
	}

	/**
	 * Converts the x,y pixel coordinates given into the Location within the {@link AppearanceMap}
	 * currently being displayed that the pixel falls within.
	 * 
	 * @param x
	 *            the x coordinate.
	 * @param y
	 *            the y coordinate.
	 * @return the {@link Location} of the x,y specified.
	 */
	public Location locationOf(int x, int y)
	{
		x -= this.xOffset;
		y -= this.yOffset;
		return this.lastScroll.translate(x / this.cellWidth, y / this.cellHeight);
	}

	/**
	 * Assigns the AppearanceGrid that should be displayed. Includes an automatic {@link #repaint()}
	 * call.
	 * 
	 * @param appearanceMap
	 *            the AppearanceGrid to show.
	 */
	public void setAppearanceMap(AppearanceMap appearanceMap)
	{
		this.appearanceMap = appearanceMap;
		this.repaint();
	}

	/**
	 * @see javax.swing.JComponent#setFont(java.awt.Font)
	 */
	@Override
	public void setFont(Font font)
	{
		super.setFont(font);
		FontMetrics fm = this.getFontMetrics(this.getFont());
		this.cellWidth = fm.charWidth('=');
		this.cellHeight = fm.getHeight() + this.lineOffset;
		this.repaint();
	}

	/**
	 * How many pixels of space to add or subtract from the height of every single line that gets
	 * displayed. Adding space makes the lines spread out vertically, and subtracting space makes
	 * them closer vertically. Not all fonts will use the same value here, so you'll have to
	 * experiement when picking your font.
	 * 
	 * @param lineOffset
	 *            the lineOffset to set
	 */
	public void setLineOffset(int lineOffset)
	{
		// cellHeight depends on lineOffset, so we have to take care when changing lineOffset.
		this.cellHeight -= this.lineOffset;
		this.lineOffset = lineOffset;
		this.cellHeight += this.lineOffset;
		this.repaint();
	}

	/**
	 * Shifts the x position of the displayed region within the panel's space by this many pixels.
	 * 
	 * @param xOffset
	 *            the xOffset to set
	 */
	public void setxOffset(int xOffset)
	{
		this.xOffset = xOffset;
		this.repaint();
	}

	/**
	 * Shifts the y position of the displayed region within the panel's space by this many pixels.
	 * 
	 * @param yOffset
	 *            the yOffset to set
	 */
	public void setyOffset(int yOffset)
	{
		this.yOffset = yOffset;
		this.repaint();
	}

	/**
	 * I've seen people create a similar style output using double buffering and stuff, but
	 * graphical FPS is just not an issue man.
	 * 
	 * @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
	 */
	@Override
	protected void paintComponent(Graphics g)
	{
		super.paintComponent(g);

		// Fill the background
		Rectangle clipBounds = g.getClipBounds();
		g.setColor(Color.black);
		g.fillRect(clipBounds.x, clipBounds.y, clipBounds.width, clipBounds.height);

		final int cellCountX = clipBounds.width / this.cellWidth;
		final int cellCountY = clipBounds.height / this.cellHeight;

		this.lastScroll = this.appearanceMap.getDisplayStartLocation(cellCountX, cellCountY);

		// Iterate and display all the text.
		for (int x = 0; x * this.cellWidth < clipBounds.width; x++)
		{
			for (int y = 0; y * this.cellHeight < clipBounds.height; y++)
			{
				Appearance a = this.appearanceMap.getAppearanceAt(this.lastScroll.translate(x, y));
				if (a == null)
				{
					continue;
				}
				this.printNormal(g, a.character + "", x * this.cellWidth + this.xOffset, (y + 1)
						* this.cellHeight + this.yOffset, a.color);
			}
		}
	}

	/**
	 * Prints a string so that the text is hollow (the background is used for the middle).
	 * 
	 * @param g
	 *            the graphics to use.
	 * @param s
	 *            the string to print.
	 * @param x
	 *            the x pixel to print at.
	 * @param y
	 *            the y pixel to print at.
	 * @param c
	 *            the color to print with.
	 */
	protected void printHollow(Graphics g, String s, int x, int y, Color c)
	{
		g.setColor(c);
		g.drawString(s, x - 1, y - 1);
		g.drawString(s, x - 1, y + 1);
		g.drawString(s, x + 1, y - 1);
		g.drawString(s, x + 1, y + 1);
		g.setColor(Color.black);
		g.drawString(s, x, y);
	}

	/**
	 * Prints a string in the normal style.
	 * 
	 * @param g
	 *            the graphics to use.
	 * @param s
	 *            the string to print.
	 * @param x
	 *            the x pixel to print at.
	 * @param y
	 *            the y pixel to print at.
	 * @param c
	 *            the color to print with.
	 */
	protected void printNormal(Graphics g, String s, int x, int y, Color c)
	{
		g.setColor(c);
		g.drawString(s, x, y);
	}
}
